﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Security;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace LocalWindowsService
{


   
        /// <summary>
        /// Class that allows running applications with full admin rights. In
        /// addition the application launched will bypass the Vista UAC prompt.
        /// </summary>
        public class ApplicationLoader
        {
            #region Structures

            [StructLayout(LayoutKind.Sequential)]
            public struct SECURITY_ATTRIBUTES
            {
                public int Length;
                public IntPtr lpSecurityDescriptor;
                public bool bInheritHandle;
            }

            [StructLayout(LayoutKind.Sequential)]
            public struct STARTUPINFO
            {
                public int cb;
                public String lpReserved;
                public String lpDesktop;
                public String lpTitle;
                public uint dwX;
                public uint dwY;
                public uint dwXSize;
                public uint dwYSize;
                public uint dwXCountChars;
                public uint dwYCountChars;
                public uint dwFillAttribute;
                public uint dwFlags;
                public short wShowWindow;
                public short cbReserved2;
                public IntPtr lpReserved2;
                public IntPtr hStdInput;
                public IntPtr hStdOutput;
                public IntPtr hStdError;
            }

            [StructLayout(LayoutKind.Sequential)]
            public struct PROCESS_INFORMATION
            {
                public IntPtr hProcess;
                public IntPtr hThread;
                public uint dwProcessId;
                public uint dwThreadId;
            }

            #endregion

            #region Enumerations

            enum TOKEN_TYPE : int
            {
                TokenPrimary = 1,
                TokenImpersonation = 2
            }

            enum SECURITY_IMPERSONATION_LEVEL : int
            {
                SecurityAnonymous = 0,
                SecurityIdentification = 1,
                SecurityImpersonation = 2,
                SecurityDelegation = 3,
            }

            #endregion

            #region Constants

            //public const int TOKEN_DUPLICATE = 0x0002;
            public const uint MAXIMUM_ALLOWED = 0x2000000;
            public const int CREATE_NEW_CONSOLE = 0x00000010;
            public const int CREATE_UNICODE_ENVIRONMENT = 0x00000400;

            public const int NORMAL_PRIORITY_CLASS = 0x20;
            //public const int IDLE_PRIORITY_CLASS = 0x40;        
            //public const int HIGH_PRIORITY_CLASS = 0x80;
            //public const int REALTIME_PRIORITY_CLASS = 0x100;

            #endregion

            #region Win32 API Imports  

            [DllImport("Userenv.dll", EntryPoint = "DestroyEnvironmentBlock",
                                        SetLastError = true)]
            private static extern bool DestroyEnvironmentBlock(IntPtr lpEnvironment);

            [DllImport("Userenv.dll", EntryPoint = "CreateEnvironmentBlock",
                                        SetLastError = true)]
            private static extern bool CreateEnvironmentBlock(ref IntPtr lpEnvironment,
                                                        IntPtr hToken, bool bInherit);

            [DllImport("kernel32.dll", EntryPoint = "CloseHandle", SetLastError = true)]
            private static extern bool CloseHandle(IntPtr hSnapshot);

            [DllImport("kernel32.dll", EntryPoint = "WTSGetActiveConsoleSessionId")]
            static extern uint WTSGetActiveConsoleSessionId();

            [DllImport("Kernel32.dll", EntryPoint = "GetLastError")]
            private static extern uint GetLastError();

            [DllImport("Wtsapi32.dll", EntryPoint = "WTSQueryUserToken", SetLastError = true)]
            private static extern bool WTSQueryUserToken(uint SessionId, ref IntPtr hToken);

            [DllImport("advapi32.dll", EntryPoint = "CreateProcessAsUser", SetLastError = true,
                                        CharSet = CharSet.Unicode,
                                        CallingConvention = CallingConvention.StdCall)]
            public static extern bool CreateProcessAsUser(IntPtr hToken,
                                                          String lpApplicationName,
                                                          String lpCommandLine,
                                                          ref SECURITY_ATTRIBUTES lpProcessAttributes,
                                                          ref SECURITY_ATTRIBUTES lpThreadAttributes,
                                                          bool bInheritHandle,
                                                          int dwCreationFlags,
                                                          IntPtr lpEnvironment,
                                                          String lpCurrentDirectory,
                                                          ref STARTUPINFO lpStartupInfo,
                                                          out PROCESS_INFORMATION lpProcessInformation);

            [DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx")]
            public static extern bool DuplicateTokenEx(IntPtr ExistingTokenHandle, uint dwDesiredAccess,
                ref SECURITY_ATTRIBUTES lpThreadAttributes, int TokenType,
                int ImpersonationLevel, ref IntPtr DuplicateTokenHandle);

            #endregion

            /// <summary>
            /// Launches the given application with full admin rights, and in addition bypasses the Vista UAC prompt
            /// </summary>
            /// <param name="commandLine">A command Line to launch the application</param>
            /// <param name="procInfo">Process information regarding the launched application that gets returned to the caller</param>
            /// <returns></returns>
            public static bool StartProcessAndBypassUac(String commandLine, out PROCESS_INFORMATION procInfo)
            {
                IntPtr hUserTokenDup = IntPtr.Zero, hPToken = IntPtr.Zero, hProcess = IntPtr.Zero;
                procInfo = new PROCESS_INFORMATION();

                // obtain the currently active session id; every logged on user in the system has a unique session id
                uint dwSessionId = WTSGetActiveConsoleSessionId();

                if (!WTSQueryUserToken(dwSessionId, ref hPToken))
                {
                    return false;
                }

                SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
                sa.Length = Marshal.SizeOf(sa);

                // copy the access token of the dwSessionId's User; the newly created token will be a primary token
                if (!DuplicateTokenEx(hPToken, MAXIMUM_ALLOWED, ref sa, (int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,
                    (int)TOKEN_TYPE.TokenPrimary, ref hUserTokenDup))
                {
                    CloseHandle(hPToken);
                    return false;
                }

                IntPtr EnvironmentFromUser = IntPtr.Zero;
                if (!CreateEnvironmentBlock(ref EnvironmentFromUser, hUserTokenDup, false))
                {
                    CloseHandle(hPToken);
                    CloseHandle(hUserTokenDup);
                    return false;
                }

                // By default CreateProcessAsUser creates a process on a non-interactive window station, meaning
                // the window station has a desktop that is invisible and the process is incapable of receiving
                // user input. To remedy this we set the lpDesktop parameter to indicate we want to enable user 
                // interaction with the new process.
                STARTUPINFO si = new STARTUPINFO();
                si.cb = (int)Marshal.SizeOf(si);
                si.lpDesktop = @"winsta0\default";

                // flags that specify the priority and creation method of the process
                int dwCreationFlags = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT;

                // create a new process in the current user's logon session
                bool result = CreateProcessAsUser(hUserTokenDup,        // client's access token
                                                null,                   // file to execute
                                                commandLine,            // command line
                                                ref sa,                 // pointer to process SECURITY_ATTRIBUTES
                                                ref sa,                 // pointer to thread SECURITY_ATTRIBUTES
                                                false,                  // handles are not inheritable
                                                dwCreationFlags,        // creation flags
                                                EnvironmentFromUser,    // pointer to new environment block 
                                                null,                   // name of current directory 
                                                ref si,                 // pointer to STARTUPINFO structure
                                                out procInfo            // receives information about new process
                                                );

                // invalidate the handles
                CloseHandle(hPToken);
                CloseHandle(hUserTokenDup);
                DestroyEnvironmentBlock(EnvironmentFromUser);

                return result; // return the result
            }
        }
    }

